﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace V2 {
    public struct Pair< T > {
        public readonly T Left;
        public readonly T Right;

        public Pair( T left, T right ) {
            Left = left;
            Right = right;
        }
    }

    public class Values {
        private readonly IComparable[] m_FieldValues;
        private readonly Pair< int >[] m_PartBounds;
        private readonly Values[] m_ValuesParts;
        private readonly Object m_External;

        private static readonly Object AS_INVAID_ASSOCIATED = new Object();
        private static readonly Values[] AsInvalidValuesParts = new Values[0];
        private static readonly IComparable[] AS_INVALID_FIELDS = new IComparable[0];
        public static readonly Values EMPTY_VALUES = new Values( AS_INVALID_FIELDS );

        public Values( Values[] values ) {
            
            m_ValuesParts = values;
            int total_field_length = 0;
            m_PartBounds = new Pair< int >[values.Length];

            for( int i = 0 ; i < values.Length ; i++ ) {
                if( values[i] is CombinedValues ) {
                    throw new NotImplementedException("Check this, it may not be implemented properly");
                }
                m_PartBounds[ i ] = new Pair< int >( total_field_length, total_field_length + values[ i ].FieldCount );
                total_field_length += values[ i ].FieldCount;
            }

            m_FieldValues = AS_INVALID_FIELDS;
            m_External = AS_INVAID_ASSOCIATED;
        }

        public Values( IComparable[] field_values ) {
            m_FieldValues = field_values;
            m_External = AS_INVAID_ASSOCIATED;
            m_ValuesParts = AsInvalidValuesParts;
        }

        public Values( Object external, IComparable[] field_values ) {
            m_FieldValues = field_values;
            m_External = external;
            m_ValuesParts = AsInvalidValuesParts;
        }

        public int FieldCount {
            get {
                if( m_FieldValues == AS_INVALID_FIELDS ) {
                    if ( m_ValuesParts == AsInvalidValuesParts ) return 0;

                    return m_PartBounds[ m_PartBounds.Length - 1 ].Right;
                } else {
                    return m_FieldValues.Length;
                }
            }
        }

        public IComparable GetFieldValue( int index ) {
            if( m_FieldValues == AS_INVALID_FIELDS ) {
                // TODO: currently GetPartIndex is horrible, so this call is very slow.
                int part_index = GetPartIndex( index );
                return m_ValuesParts[ part_index ].GetFieldValue( index - m_PartBounds[ part_index ].Left );
            } else {
                return m_FieldValues[ index ];
            }
        }

        private int GetPartIndex( int index ) {
            // TODO: horrible.
            for( int i = 0 ; i < m_PartBounds.Length ; i++ ) {
                if( m_PartBounds[ i ].Left <= index && m_PartBounds[ i ].Right > index ) {
                    return i;
                }
            }
            throw new ArgumentOutOfRangeException( "index", index, "no field mapped to this part_index" );
        }

        public int PartCount {
            get {
                if( m_ValuesParts == AsInvalidValuesParts ) return 0;
                return m_ValuesParts.Length;
            }
        }

        public Values GetPart( int index ) {
            if( m_ValuesParts == AsInvalidValuesParts ) throw new Exception( "Values has no Parts" );
            return m_ValuesParts[ index ];
        }

        public Object External { get { return m_External; } }

        public Object GetExternal( int part_index ) {
            if( m_ValuesParts == AsInvalidValuesParts ) throw new Exception( "No Values parts" );
            return m_ValuesParts[ part_index ].External;
        }

        public bool IsExternalMapped( int part_index ) { return m_ValuesParts[ part_index ].External != AsInvalidValuesParts; }

        public CombinedValues ClearPart( int part_index ) {
            Values values_part = GetPart( part_index );
            Values empty = values_part.CreateEmpty();
            
            return ReplacePart(part_index, empty);
        }

        protected IComparable[] CopyFields() {
            IComparable[] fields = new IComparable[ m_FieldValues.Length ];
            Array.Copy( m_FieldValues, fields, m_FieldValues.Length );
            return fields;
        }

        protected Values CreateEmpty() {
            if( PartCount == 0 ) {
                IComparable[] fields = new IComparable[ m_FieldValues.Length ];
                return new AdhocValues( fields );
            }else {
                Values[] values_parts = new Values[ m_ValuesParts.Length ];
                for (int i = 0; i < values_parts.Length; i++) values_parts[i] = m_ValuesParts[ i ].CreateEmpty();
                return new CombinedValues( values_parts );
            }
        }

        public CombinedValues ReplacePart( int part_index, Values addition ) {
            Values[] underlying = new Values[PartCount];
            for( int i = 0 ; i < underlying.Length ; i++ ) {
                if( i == part_index ) underlying[ i ] = addition;
                else underlying[ i ] = m_ValuesParts[ i ];
            }
            CombinedValues values = new CombinedValues( underlying );
            return values;
        }

        public MaskedValues GetMaskedValues( bool[] is_included, Values values ) {
            if( values.PartCount > 0 ){ throw new NotImplementedException( "Masked Values not implemented for ValueParts");}
            IComparable[] fields = new IComparable[ values.FieldCount ];
            for( int i=0 ; i < fields.Length ; i++ ) {
                if( is_included[i] == false ) continue;
                fields[ i ] = values.GetFieldValue( i );
            }
            return new MaskedValues( is_included, fields );
        }

        #region Implementation of IComparable

        public int CompareTo( object obj ) {
            Values rhs = obj as Values;
            return CompareTo( rhs );
        }

        public int CompareTo( Values other ) {
            if( ReferenceEquals( other, null ) ) {
                throw new NotImplementedException( "Unsure of what this means" );
            }

            if( m_FieldValues.Length != other.m_FieldValues.Length ) {
                throw new NotImplementedException( "Unsure of what this means" );
            }

            int compare = 0;
            if (m_FieldValues != AS_INVALID_FIELDS) {
                for( int i = 0 ; i < m_FieldValues.Length ; i++ ) {
                    if( ReferenceEquals( m_FieldValues[ i ], null ) ) {
                        if( ReferenceEquals( other.m_FieldValues[ i ], null ) ) continue;
                        else return -1;
                    }
                    compare = m_FieldValues[ i ].CompareTo( other.m_FieldValues[ i ] );
                    if( compare != 0 ) return compare;
                }
            }else if( m_ValuesParts != AsInvalidValuesParts ) {
                compare = other.m_ValuesParts.Length - m_ValuesParts.Length;
                if( compare != 0 ) return compare;

                for(int i=0 ; i < m_ValuesParts.Length ; i++ ) {
                    compare = m_ValuesParts[ i ].CompareTo( other.m_ValuesParts[ i ] );
                    if (compare != 0) return compare;
                }
            }
            return compare;
        }

        #endregion

        #region Equality and HashCode

        private bool Equals( Object[] rhs ) {
            if( rhs.Length != m_FieldValues.Length ) return false;

            for( int i = 0 ; i < m_FieldValues.Length ; i++ ) {
                if( m_FieldValues[ i ] == null ) {
                    if( rhs[ i ] != null ) return false;
                } else if( m_FieldValues[ i ].Equals( rhs[ i ] ) == false ) return false;
            }
            return true;
        }

        public bool Equals( Values other ) {
            if( ReferenceEquals( null, other ) ) return false;
            if( ReferenceEquals( this, other ) ) return true;

            if (m_ValuesParts.Length != other.m_ValuesParts.Length) return false;
            for (int i = 0; i < m_ValuesParts.Length; i++ ) {
                if (Equals(m_ValuesParts[i], other.m_ValuesParts[i]) == false) return false;
            }
            if (FieldCount != other.FieldCount) return false;
            return Equals(other.m_FieldValues);
        }

        public override bool Equals( object obj ) {
            if( ReferenceEquals( null, obj ) ) return false;
            if( ReferenceEquals( this, obj ) ) return true;

            //if( obj.GetType() != typeof( Values ) ) return false;
            if( obj is Values == false ) return false;
            return Equals( ( Values ) obj );
        }

        public override int GetHashCode() {
            unchecked {
                int result = 0;
                for( int i = 0 ; i < m_FieldValues.Length ; i++ ) {
                    result = ( result*397 ) ^
                             ( m_FieldValues[ i ] != null ? m_FieldValues[ i ].GetHashCode() : 0 );
                }
                return result;
            }
        }

        #endregion
    }


    public class ExternalUniqueValues : Values {
        public ExternalUniqueValues( Object associated, IComparable[] values ) : base( associated, values ) { }
    }

    public class AdhocValues : Values {
        public AdhocValues( params IComparable[] values ) : base( values ) { }

        public override string ToString() {
            StringBuilder sb = new StringBuilder();
            sb.Append( "Adhoc{" );
            for( int i = 0 ; i < FieldCount ; i++ ) {
                sb.Append( GetFieldValue( i ) );
                if( i + 1 < FieldCount )
                    sb.Append( ", " );
            }
            sb.Append( "}" );
            return sb.ToString();
        }
    }

    public class CombinedValues : Values {
        public CombinedValues( Values[] values ) : base( values ) { }
        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("Combo{");
            for (int i = 0; i < PartCount; i++  ) {
                sb.Append( GetPart( i ) );
                if (i + 1 < PartCount)
                    sb.Append(", ");
            }
            sb.Append("}");
            return sb.ToString();
        }
    }

    public class MaskedValues : Values {

        private readonly bool[] m_IsIncluded;
        
        public MaskedValues( bool[] is_included, IComparable[] fields ) : base( fields ) {
#if DEBUG
            for (int i = 0; i < is_included.Length; i++)
            {
                if (fields[i] != null && is_included[i] == false)
                {
                    throw new ArgumentException("fields argument contains value where is_included is false");
                }
            }
#endif
            m_IsIncluded = is_included;
        }
        
        public bool[] IsIncluded { get { return m_IsIncluded; } }

        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("MaskedValues{");
            for (int i = 0; i < FieldCount; i++)
            {
                if (m_IsIncluded[i] ) {
                    sb.Append(GetFieldValue(i));
                }
                if (i + 1 < FieldCount)
                    sb.Append(", ");
            }
            sb.Append("}");
            return sb.ToString();
        }
    }
}